package hebClustering;
import hebClustering.nlp.Corpus;
import hebClustering.vectorSpace.DocumentVector;
import hebClustering.vectorSpace.IVector;
import hebClustering.vectorSpace.SparseVector;
import hebClustering.vectorSpace.distances.ChebyshevDistance;
import hebClustering.vectorSpace.distances.EuclidianDistance;
import hebClustering.vectorSpace.distances.IDistance;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;


/**
 * Cluster is the class which represents the set of vectors which is part
 * of the clustering problem. 
 * 
 * 
 * The common grounds between vectors in this group is they are supposed to
 * be highly similar to one another.
 * This class includes all the functionality necessary in order to measure this
 * similarity and help find it.
 * */
public class Cluster extends HashSet<IVector> {
	
	

	private static final long serialVersionUID = 1L;
	private IVector mean;
	private IVector representative;
	
	
	/**
	 *	Builds a cluster whose mean is center.
	 *
	 *	@param center - a mean for the cluster.
	 */
	public Cluster(IVector center) {
		setMean(center);
	}

	/**
	 *	Builds an empty cluster.
	 */
	public Cluster() {}

	/**
	 *	Returns a list of the top ten most relevant vectors.
	 *
	 * 	<p>The vectors in the list are those who are the closest 
	 * 	to the centroid and they are sorted in acceding order.</p>
	 * 
	 * @return         A list with 10 elements of vectors in the cluster.
	 */
	public List<IVector> getTopTen(){
		List<IVector> topTen = new LinkedList<IVector>();
		IDistance distance = new ChebyshevDistance();
		
		if (size() < 10){
			topTen.addAll(this);
		}else{
			IVector arr[] = new DocumentVector[size()];
			toArray(arr);
			Arrays.sort(arr,new CentroidComperator(findCentroid(),distance));
			for (int i = 0; i < 10; i++){
				topTen.add(arr[i]);
			}
		}
		
		return topTen;
	}

	/**
	 *	An internal comparator of vectors in the cluster
	 *	This comparator compares two vectors by their distance from the cluster's
	 *	centroid. 
	 */
	private class CentroidComperator implements Comparator<IVector>{

		private IVector centroid;
		private IDistance d;

		public CentroidComperator(IVector centroid,IDistance d) {
			this.centroid = centroid;
			this.d = d;
		}

		@Override
		public int compare(IVector o1, IVector o2) {
			double res = d.calc(o1, centroid) - d.calc(o2, centroid);
			if (res < 0)
				return -1;
			else if (res > 0)
				return 1;
			else
				return 0;
		}

	}

	/**
	 *	Returns the centroid of the cluster.
	 *
	 * 	<p>The method computes a vector which is the centroid of the cluster
	 * 	as it is defined for a finite set of points. <br></p>
	 * 
	 *	See <a href="http://en.wikipedia.org/wiki/Centroid" target="_blank">Centroid</a> on Wikipedia for more information.
	 * 
	 * @return         The vector which represents the cluster's centroid.
	 * @see #findMedian()
	 */
	public IVector findCentroid() {
		int dimension = Corpus.getCorpus().getNumOfTokens();
		IVector centroid = new SparseVector(dimension);

		for(int i=0; i<dimension; i++){
			double sum = 0;

			for(IVector vector : this){
				sum+=vector.ithValue(i);
			}

			centroid.addToVector(i, sum/size());
		}

		return centroid;
	}

	
	/**
	 *	Returns the median of the cluster.
	 *
	 * 	<p>The method computes a vector which is the median of the cluster
	 * 	which has been defined in the following way:
	 * 
	 *	The i-th coordinate of the vector is the median of all i-th coordinates
	 *	of the vectors in the cluster.</p>
	 * 
	 * See <a href="http://en.wikipedia.org/wiki/Median" target="_blank">Median</a> on Wikipedia for more information.
	 * 
	 * @return         The vector which represents the cluster's median
	 * @see #findCentroid()
	 */
	public IVector findMedian() {
		int dimension = Corpus.getCorpus().getNumOfTokens();
		IVector median = new SparseVector(dimension);
		double x;
		int size = size();

		for(int i=0; i<dimension; i++){
			List<Double> data = new ArrayList<Double>(size);
			Collections.sort(data);

			for(IVector vector : this){
				data.add(vector.ithValue(i));
			}

			if(size%2==0)
				x = 0.5*(data.get(size/2-1)+data.get(size/2));
			else x = data.get(size/2);

			median.addToVector(i, x);
		}

		return median;
	}

	/**
	 *	Set the mean for the cluster.
	 *
	 * <p>Sets a vector as a mean for the cluster. <br>
	 * The mean is a representative for all the vectors and used mainly for
	 * implementing various clustering algorithms. <br>
	 * The mean isn't necessarily the equals to median or the centroid at all times. <br>
	 * The mean can be retrieved using {@link #getMean() getMean}.</p>
	 * 
	 * @param mean - A vector to be set as the mean of the cluster.
	 * 
	 * @see #findCentroid()
	 * @see #findMedian()
	 */
	public void setMean(IVector mean) {
		this.mean = mean;
	}

	/**
	 *	Get the mean for the cluster.
	 *
	 *<p>Get a vector as a mean for the cluster. <br>
	 * The mean is a representative for all the vectors and used mainly for
	 * implementing various clustering algorithms. <br>
	 * The mean isn't necessarily equals to the median or the centroid at all times. <br>
	 * The mean can be set using {@link #setMean(IVector) setMean}.</p>
	 * 
	 * @return The current cluster's mean.
	 * 
	 * @see #findCentroid()
	 * @see #findMedian()
	 */
	public IVector getMean(){
		return mean;
	}
	
	/**
	 *	Get the representative for the cluster.
	 *
	 *<p>Get a vector that is the current representative for the cluster. <br>
	 * The representative is a vector that represents the vectors in a cluster and is used mainly for
	 * implementing various clustering algorithms. <br>
	 * The representative can be set using {@link #setRepresentative(IVector) setRepresentative}.</p>
	 * 
	 * @return The current cluster's representative.
	 */
	public IVector getRepresentative() {
		return representative;
	}

	/**
	 *	Set the representative for the cluster.
	 *
	 * <p>Sets a vector as a representative for the cluster. <br>
	 * The representative is a vector that represents the vectors in a cluster and is used mainly for
	 * implementing various clustering algorithms. <br>
	 * The mean can be retrieved using {@link #getRepresentative() getRepresentative}.</p>
	 * 
	 * @param representative - A vector to be set as the mean of the cluster.
	 * 
	 * @see #findCentroid()
	 * @see #findMedian()
	 */
	public void setRepresentative(IVector representative) {
		this.representative = representative;
	}

	/**
	 *	Get a random vector from the cluster.
	 *
	 * @return A random vector from the cluster.
	 */
	public IVector getRandomVector() {
		List<IVector> vecLst = new ArrayList<IVector>(this);
		
		Collections.shuffle(vecLst);
		
		return vecLst.get(0);
	}
	
	/**
	 *	Computes the sum squares of errors.
	 *
	 * 	<p>The sum squares of errors for a cluster is computed by summing the distance <br>
	 * 	of each vector from the centroid.</p>
	 * 
	 * @return         The sum squares of errors.
	 */
	public double getSSE() {
		IVector center = findCentroid();
		double sum=0;
		IDistance dist = new EuclidianDistance();
		
		for(IVector v : this){
			sum+=dist.calc(v, center);
		}	
		return sum;
	}

	
	/**
	 *	A string representation of the cluster.
	 *
	 * @return         String representation.
	 */
	public String toString(){
		String output = "";
		output += "Cluster size: " + size() + "\r\n\t\t";
		output += "Cluster representative: " + representative + "\r\n\t\t\t";
		output += "Quality: " + getSSE() + "\r\n\t\t\t";
		for (IVector v : this){
			output += v.toString() + "\r\n\t\t\t";
		}		

//		for (IVector v : getTopTen()){
//			output += v.toString() + "\r\n\t\t\t";
//		}
		return output;
	}

}
